from DataAccess.DBConnFactory import DBConnFactory
from Common.Singleton import Singleton

#import sys
from datetime import date, datetime, timedelta
from string import Template


class Frequency:	
	Daily 		= 365
	Weekly 		= 52
	Monthly 	= 12
	Quarterly	= 4
	Semiannual	= 2
	Annual		= 1
	Once		= 0
	

class TimeUnit:
	Days 		= 1
	Weeks		= 2
	Months		= 3
	Years		= 4
	
	
class RollingConv:
	Following = 0
	Preceding = 1

	
	
class TradingCalendar(Singleton):
	def init_only_once(self):
		results = query_trading_date(date(2005,1,1), date.today())
		self.min_trading_date = results[0]
		self.max_trading_date = results[-1]
		self.trading_day_set = frozenset(results)
		#print "debug, trading date list:", max(self.trading_day_set)
		#pirnt self.trading_day_set
		
	def is_trading_day(self, date):
		if date>=self.min_trading_date and date<=self.max_trading_date:
			return date in self.trading_day_set
		elif date.weekday() < 5:
			return True
		else:
			return False
				
	
	def trading_days_between(self, start_date, end_date):
		days = 0
		d = start_date
		one_day = timedelta(days=1)
		while d<end_date:
			if self.is_trading_day(d):
				days += 1
			d += one_day
		
		return days

		
	def is_holiday(self, date):
		return not self.is_trading_day(date)

		
	def nearest_trading_day(self, date, rolling_conv):
		one_day = timedelta(days=1)
		
		nearest = date			
		while(self.is_holiday(nearest)):
			if rolling_conv == RollingConv.Following:
				nearest = nearest + one_day
			elif rolling_conv == RollingConv.Preceding:
				nearest = nearest - one_day	
			else:
				pass
	
		return nearest
	
	
	def advance(self, ref_date, time_unit, unit_num, rolling_conv):	
		before_rolling = ref_date
		if time_unit == TimeUnit.Days:
			before_rolling = ref_date + timedelta(days=unit_num)
		elif time_unit == TimeUnit.Weeks:
			before_rolling = ref_date + timedelta(days=7*unit_num)
		elif time_unit == TimeUnit.Months:
			before_rolling = offset_date_by_month(ref_date, unit_num)
		elif time_unit == TimeUnit.Years:
			before_rolling = offset_date_by_month(ref_date, 12*unit_num)
		else:
			pass

		return self.nearest_trading_day(before_rolling, rolling_conv)
		
		
#if __name__ == '__main__':
#	cldr = TradingCalendar()
	
def query_trading_date(start_date, end_date):
	conn = DBConnFactory().get_db_connection('FINCHINA')
	cursor = conn.cursor()
	sql_tpl = Template('''select tdate 
						from tradedate
						where exchange='CNSESH'
						and tdate>=to_date('${START_DATE}','yyyy-mm-dd')
						and tdate<to_date('${END_DATE}','yyyy-mm-dd')
						order by tdate''')
	sql_text = sql_tpl.substitute(START_DATE=start_date.isoformat(), 
								END_DATE=end_date.isoformat())
	cursor.execute(sql_text)
	return [i[0].date() for i in cursor.fetchall()]

	
def offset_date_by_month(d, offset_months):
	one_day = timedelta(days=1)
	q,r = divmod(d.month + offset_months, 12)	
	d2 = date(d.year+q,r+1,1) - one_day
	
	if d.month != (d+one_day).month:
		return d2
		
	if d.day >= d2.day:
		return d2
		
	return d2.replace(day=d.day)
	
	
	
def convert_freq_to_period(freq):
	if freq==Frequency.Daily:
		return (TimeUnit.Days,1)
	elif freq==Frequency.Weekly:
		return (TimeUnit.Weeks,1)
	elif freq==Frequency.Monthly:
		return (TimeUnit.Months,1)
	elif freq==Frequency.Quarterly:
		return (TimeUnit.Months,3)
	elif freq==Frequency.Semiannual:
		return (TimeUnit.Months,6)
	elif freq==Frequency.Annual:
		return (TimeUnit.Months,12)
	else:
		pass


		
# def adj_to_trading_date(trd_bitmap, low_basis, date_list, adj_conv='FOLLOWING'):
	# result = []
	# for d in date_list:
		# cursor = conv_date_to_num(d)-low_basis
		# while trd_bitmap[cursor]==0:
			# cursor += 1
		
		# result.append(conv_num_to_date(cursor+low_basis))
	
	# return result

	
# def create_trading_date_bitmap(trading_days):
	# low_bound = conv_date_to_num(trading_days[0])
	# upper_bound = conv_date_to_num(trading_days[-1])
	
	# #array()
	# bitmap = [0]*(upper_bound-low_bound+1)
	# for d in trading_days:
		# bitmap[conv_date_to_num(d)-low_bound] = 1
		
	# return (bitmap, low_bound)	

	
# def conv_date_to_num(ref_date):
	# return ref_date.year*10000+ref_date.month*100+ref_date.day

	
# def conv_num_to_date(num):
	# y = num/10000
	# m = (num-y*10000)/100
	# d = num-y*10000-m*100
	# return date(y, m, d)
	
	
